package com.example.ortools.start.cp;

import com.google.ortools.Loader;
import com.google.ortools.sat.*;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

public class CpDealDlyEmample {

    public static void main(String[] args) {


        //规格id最大数量限制
        Map<Long, BigDecimal> specIdMaxNumLimit = new HashMap<>();
        //规格id拆零单位数据
        Map<Long, Integer> specIdSingleNum = new HashMap<>();
        //规格id最大整包数量
        Map<Long, Integer> specIdMaxIntNum = new HashMap<>();
        List<MergeData> req = new ArrayList<>();

        cpSolver(specIdMaxIntNum, req);

    }

    public static void cpSolver(Map<Long, Integer> specIdMaxIntNum, List<MergeData> req) {
        Loader.loadNativeLibraries();

        Map<Integer, List<MergeData>> listMap = req.stream().collect(Collectors.groupingBy(MergeData::getGroupId));
        listMap.forEach((groupId, mergeDataList) -> {
            //创建CP模型求解器
            CpModel model = new CpModel();
            //初始化变量存储集合
            Map<String,TempHelper> allVarMap = new HashMap<>();
            //初始化共享变量存储集合
            Map<Long,List<IntVar>> sharedVarMap = new HashMap<>();
            //初始化变量名称与拆零单位映射集合
            Map<String,BigDecimal> valNameBySingleNumMap = new HashMap<>();
            mergeDataList.forEach(mergeData -> {
                //初始化变量
                initMPVariable(specIdMaxIntNum, model, allVarMap, sharedVarMap, valNameBySingleNumMap, mergeData);
                //设置系数限制条件
                setRatioCoefficient(model, allVarMap, mergeData);
            });
            //设置共享限制条件
            setSharedCofficient(specIdMaxIntNum, model, sharedVarMap);
            //构造求解目标
            CpSolver solver = makeCPObjective(model, allVarMap);
            //求解输出
            executeSolver(model, allVarMap, solver);
        });
    }

    private static void executeSolver(CpModel model, Map<String, TempHelper> allVarMap, CpSolver solver) {
        CpSolverStatus status = solver.solve(model);
        if (status == CpSolverStatus.OPTIMAL || status == CpSolverStatus.FEASIBLE) {
            System.out.printf("Maximum of objective function: %f%n", solver.objectiveValue());
            for (TempHelper value : allVarMap.values()) {
                IntVar var = value.getVar();
                System.out.println(var.getName() + ": " + new BigDecimal(solver.value(var)).multiply(value.getSplitUseRatio()));
            }

            System.out.println("Problem solved in " + solver.wallTime() + " milliseconds");
        } else {
            System.out.println("No solution found.");
        }
    }

    private static CpSolver makeCPObjective(CpModel model, Map<String, TempHelper> allVarMap) {
        LinearArgument[] allVars = new LinearArgument[allVarMap.size()];
        long[] weightArray = new long[allVarMap.size()];
        int i=0;
        for (TempHelper value : allVarMap.values()) {
            allVars[i]=value.getVar();
            weightArray[i]=value.getSplitUseRatio().longValue();
            i++;
        }

        model.maximize(LinearExpr.weightedSum(allVars,weightArray));
        CpSolver solver = new CpSolver();
        return solver;
    }

    private static void setSharedCofficient(Map<Long, Integer> specIdMaxIntNum, CpModel model, Map<Long, List<IntVar>> sharedVarMap) {
        sharedVarMap.forEach((shareSpecId, intVarList)->{
            LinearArgument[] varList = intVarList.toArray(new IntVar[intVarList.size()]);
            long[] weightArray = new long[varList.length];
            for (int i=0;i<weightArray.length; i++) {
                weightArray[i]=1L;
            }
            model.addLessOrEqual(LinearExpr.weightedSum(varList,weightArray), specIdMaxIntNum.get(shareSpecId));
        });
    }

    private static void setRatioCoefficient(CpModel model, Map<String, TempHelper> allVarMap, MergeData mergeData) {
        for(int i = 1; i< mergeData.getGoodsSpecCombinationROList().size(); i++){
            GoodsSpecCombinationRO pre = mergeData.getGoodsSpecCombinationROList().get(i-1);
            GoodsSpecCombinationRO cur = mergeData.getGoodsSpecCombinationROList().get(i);
            IntVar preVar = null;
            IntVar curVar = null;
            String tempValName = "";
            if(pre.getShareSpecId()!=null) {
                preVar = allVarMap.get(tempValName+pre.getGoodsSpecId() + pre.getSeq()).getVar();
            }else{
                preVar = allVarMap.get(tempValName+pre.getGoodsSpecId()).getVar();
            }
            if(cur.getShareSpecId()!=null) {
                curVar = allVarMap.get(tempValName+cur.getGoodsSpecId() + cur.getSeq()).getVar();
            }else{
                curVar = allVarMap.get(tempValName+cur.getGoodsSpecId()).getVar();
            }

            model.addEquality(LinearExpr.weightedSum(new LinearArgument[]{preVar},new long[]{cur.getRatio().multiply(pre.getSplitUseRatio()).longValue()})
                    ,LinearExpr.weightedSum(new LinearArgument[]{curVar},new long[]{pre.getRatio().multiply(cur.getSplitUseRatio()).longValue()}));
        }
    }

    private static void initMPVariable(Map<Long, Integer> specIdMaxIntNum, CpModel model, Map<String, TempHelper> allVarMap, Map<Long, List<IntVar>> sharedVarMap, Map<String, BigDecimal> valNameBySingleNumMap, MergeData mergeData) {
        mergeData.getGoodsSpecCombinationROList().forEach(bo -> {
            String tempValName = "";
            IntVar tempVar = null;
            if(bo.getShareSpecId()==null){
                tempValName = tempValName + bo.getGoodsSpecId();
                tempVar = model.newIntVar(0, specIdMaxIntNum.get(bo.getGoodsSpecId()), tempValName);
            }else{
                tempValName = tempValName + bo.getGoodsSpecId()+bo.getSeq();
                tempVar = model.newIntVar(0, specIdMaxIntNum.get(bo.getGoodsSpecId()), tempValName);
                if(!sharedVarMap.containsKey(bo.getShareSpecId())){
                    sharedVarMap.put(bo.getShareSpecId(),new ArrayList<>());
                }
                sharedVarMap.get(bo.getShareSpecId()).add(tempVar);
            }
            TempHelper tempHelper = new TempHelper();
            tempHelper.setVar(tempVar);
            tempHelper.setSplitUseRatio(bo.getSplitUseRatio());
            allVarMap.put(tempValName,tempHelper);
            valNameBySingleNumMap.put(tempValName,bo.getSplitUseRatio());
        });
    }


}
